#include "MsIni.h"
#include "ByteBuffer.h"

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

/*
 * INI File List
 */
typedef map<string, ini_file_t*> INIFileList;
static INIFileList inis;

/*
 * Enable INI File Cache
 */
static bool gEnableIniFileCache = true;

/*
 * Enable Close INI File after Open Immediately
 */
static bool gEnableIniFileCloseWhenOpen = true;

/*
 * Enable INI File Cache
 */
bool EnableIniFileCache(bool enablecache) {
	return (gEnableIniFileCache = enablecache);
}

/*
 * Enable Close INI File after Open Immediately
 */
bool EnableIniFileCloseWhenOpen(bool enableclose) {
	return (gEnableIniFileCloseWhenOpen = enableclose);
}

/*
 * Parse INI File
 */
static ini_file_t* ParseIniFile(ByteBuffer& buffer) {
	const char* data = buffer.get();

	enum ParseState {
		Clear, Section, KeyValue, Annotate,
	} parse_state = Clear;

	string key_name;
	string key_value;

	ini_file_t* ini = new ini_file_t();

	string section_name;
	ini_section_t* section = NULL;

	for (size_t curpos = 0, oldpos = 0; curpos < buffer.length(); curpos++) {
		switch (data[curpos]) {
		case '[':
			if (parse_state == Clear) {
				section = new ini_section_t();
				section_name.clear();

				parse_state = Section;
				oldpos = curpos;
			}
			break;
		case ']':
			if (parse_state == Section) {
				section_name.assign(&data[oldpos + 1], curpos - oldpos - 1);
			}
			break;
		case '=':
			if (parse_state == Clear) {
				key_name.assign(&data[oldpos + 1], curpos - oldpos - 1);

				parse_state = KeyValue;
				oldpos = curpos;
			}
			break;
		case ';':
			if (parse_state == Clear) {
				parse_state = Annotate;
			}
			break;
		case '\r':
		case '\n':
			if (parse_state == Section) {
				ini->sections.insert(make_pair<>(section_name, section));
			} else if (parse_state == KeyValue) {
				key_value.assign(&data[oldpos + 1], curpos - oldpos - 1);

				assert(section != NULL);
				section->key_values.insert(make_pair<>(key_name, key_value));
			}

			parse_state = Clear;
			oldpos = curpos;
			break;
		default:
			break;
		}
	}

	return ini;
}

/*
 * Load INI file
 */
static ini_file_t* LoadIniFile(const string& filename) {
	FILE* file = fopen(filename.c_str(), "rb");
	if (file == NULL) {
		return NULL;
	}

	const size_t initsize = 512;
	char staticbuffer[initsize];

	ByteBuffer bytebuffer(initsize);

	do {

		size_t nread = fread(staticbuffer, sizeof(char), initsize, file);

		if (nread > 0) {
			bytebuffer.append(staticbuffer, nread);
		}

		if (nread < initsize) {
			if (feof(file) != 0) {
				break;
			}
		}

	} while (true);

	ini_file_t* ini = ParseIniFile(bytebuffer);

	if (gEnableIniFileCloseWhenOpen) {
		fclose(file);
	} else {
		if (ini != NULL) {
			ini->file = file;
		}
	}

	return ini;
}

/*
 * Get INI File
 */
ini_file_t* GetIniFile(const string& filename) {

	// 查找现有文件
	INIFileList::iterator iter = inis.find(filename);
	if (iter == inis.end()) {

		// 加载新的文件
		ini_file_t* newIni = LoadIniFile(filename);
		if (newIni == NULL) {
			return NULL;
		}

		if (gEnableIniFileCache) {
			// 将文件加入到列表
			pair<INIFileList::iterator, bool> it = inis.insert(
					make_pair<>(filename, newIni));
			if (!it.second) {
				delete newIni;
				return NULL;
			}
		}

		return newIni;
	} else {
		return (*iter).second;
	}
}

/*
 * Get INI File Section
 */
ini_section_t* GetIniFileSection(const string& section,
		const string& filename) {

	ini_file_t* ini = GetIniFile(filename);
	if (ini == NULL) {
		return NULL;
	}

	// 查找区域值
	ini_file_t::SectionMapList& sections = ini->sections;
	ini_file_t::SectionMapList::iterator iter = sections.find(section);
	if (iter == sections.end()) {
		return NULL;
	}

	return (*iter).second;
}

/*
 * Get INI File String
 */
string GetIniFileString(const string& section, const string& key,
		const string& def, const string& filename) {

	ini_section_t* sect = GetIniFileSection(section, filename);

	// 查找键值
	ini_section_t::KeyValueMap& key_values = sect->key_values;
	ini_section_t::KeyValueMap::iterator iter = key_values.find(key);
	if (iter == key_values.end()) {
		return def;
	}

	return (*iter).second;
}

/*
 * Get INI File Integer
 */
int32_t GetIniFileInt32(const string& section, const string& key, int32_t def,
		const string& filename) {
	string str = GetIniFileString(section, key, "", filename);
	if (str.length() == 0) {
		return def;
	}
	return atoi(str.c_str());
}

/*
 * Get INI File String
 */
bool WriteIniFileString(const string& section, const string& key,
		const string& value, const string& filename) {
	FILE* file = NULL;

	if (gEnableIniFileCache && !gEnableIniFileCloseWhenOpen) {
		INIFileList::iterator iter = inis.find(filename);
		if (iter != inis.end()) {
			file = (*iter).second->file;
		}
	}

	if (file == NULL) {
		file = fopen(filename.c_str(), "wb");
		if (file == NULL) {
			return false;
		}
	}

	assert(file != NULL);

	return false;
}

/*
 * Get INI File String
 */
bool WriteIniFileInt32(const string& section, const string& key, int32_t value,
		const string& filename) {
	return false;
}

/*
 * Clear INI File Cache
 */
void ClearIniFileCache() {
	inis.clear();
}
